/////////////////////////////////////////////////////////////////////////////
// Name:        tts_espeak.cpp
// Purpose:     A wxWidgets wrapper around eSpeak
// Author:      Julian Smart
// Modified by: 
// Created:     2009-02-10
// RCS-ID:      
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"

#include "wx/filename.h"
#include "wx/textfile.h"
#include "wx/datstrm.h"
#include "wx/wfstream.h"
#include "wx/dir.h"
#include "wx/log.h"

#include "tts/tts_espeak.h"

/*
 * wxTTSEspeakHandler implementation
 */

IMPLEMENT_DYNAMIC_CLASS( wxTTSEspeakHandler, wxTTSExternalCommandHandler )

wxTTSEspeakHandler::wxTTSEspeakHandler(): wxTTSExternalCommandHandler(wxT("eSpeak"), _("eSpeak"))
{
    Init();
}

wxTTSEspeakHandler::~wxTTSEspeakHandler()
{
}

// Member initialisation
void wxTTSEspeakHandler::Init()
{
    // Initialize generic properties

#ifdef __WXMSW__
    SetProgramLocation(wxT("speak"));
#else
    SetProgramLocation(wxT("espeak"));
#endif
    SetVoice(wxT("en-rp"));
    SetSpeed(170);      // Speed in words per minute, 80-390
    SetVolume(100);     // 0-200
    SetPitch(50);       // 0-99
    SetVoiceVariant(wxT("None"));

    SetProperty(wxTTS_PROP_INPUT_FORMAT, wxTTS_PROP_INPUT_FORMAT_TEXT);
    SetProperty(wxTTS_PROP_NO_SUBSTITUTIONS, false);
    SetProperty(wxTTS_PROP_SIMPLIFY_PUNCTUATION, false);
    SetProperty(wxTTS_PROP_TEXT_ENCODING, wxString(wxT("utf-8")));
    SetProperty(wxTTS_PROP_ENGINE_WEBSITE, wxString(wxT("http://espeak.sourceforge.net/download.html")));
    SetProperty(wxTTS_PROP_PRIORITY, 0);

    // Initialize eSpeak properties

    SetProperty(wxTTS_PROP_ESPEAK_8BIT_ENCODING, false);
    SetProperty(wxTTS_PROP_ESPEAK_QUIET_OPERATION, false);
    SetProperty(wxTTS_PROP_ESPEAK_NO_FINAL_PAUSE, false);

#ifdef __UNIX__
    wxString dataLocation = GetDataLocation();
    if (dataLocation.IsEmpty() || !wxDirExists(dataLocation))
    {
        if (!wxGetHomeDir().IsEmpty() && wxDirExists(wxGetHomeDir() + wxFILE_SEP_PATH + wxT("espeak-data")))
            dataLocation = wxGetHomeDir() + wxFILE_SEP_PATH + wxT("espeak-data");
        else if (wxDirExists(wxT("/usr/share/espeak-data")))
            dataLocation = wxT("/usr/share/espeak-data");
        
        if (!dataLocation.IsEmpty())
            SetDataLocation(dataLocation);
    }
#endif
}

// Speak the text
bool wxTTSEspeakHandler::Speak(const wxString& text, int options)
{
    return DoSpeak(text, false, options);
}

// Speak the file
bool wxTTSEspeakHandler::SpeakFile(const wxString& filename, int options, const wxString& waveFilename)
{
    return DoSpeak(filename, true, options, waveFilename);
}

// Speak the text or file
bool wxTTSEspeakHandler::DoSpeak(const wxString& textOrFilename, bool isFilename, int options, const wxString& waveFilename)
{
    // Avoid dangerous reentrancy
    if (IsStopping())
        return false;

    if (IsSpeaking())
    {
        Stop();
    }

    DeleteTempFile();

    SetOptions(options);

    m_errorCode = 0;
    m_pid = 0;

    // Get the flags - and always simplify punctuation
    int transformationFlags = CreateTransformerOptions(wxTTS_TRANSFORM_OPTIONS_SIMPLIFY_PUNCTUATION);

    wxString cmd = wxT("\"") + GetProgramLocation() + wxT("\"");

    if (!GetVoice().IsEmpty())
    {
        cmd << wxT(" -v ") << GetVoice();

        wxString variantName = GetVoiceVariant();
        wxString variantSwitch;
        if (variantName == wxT("Male 1"))
            variantSwitch = wxT("+m1");
        else if (variantName == wxT("Male 2"))
            variantSwitch = wxT("+m2");
        else if (variantName == wxT("Male 3"))
            variantSwitch = wxT("+m3");
        else if (variantName == wxT("Male 4"))
            variantSwitch = wxT("+m4");
        else if (variantName == wxT("Male 5"))
            variantSwitch = wxT("+m5");
        else if (variantName == wxT("Male 6"))
            variantSwitch = wxT("+m6");
        else if (variantName == wxT("Female 1"))
            variantSwitch = wxT("+f1");
        else if (variantName == wxT("Female 2"))
            variantSwitch = wxT("+f2");
        else if (variantName == wxT("Female 3"))
            variantSwitch = wxT("+f3");
        else if (variantName == wxT("Female 4"))
            variantSwitch = wxT("+f4");
        else if (variantName == wxT("Whisper"))
            variantSwitch = wxT("+whisper");
        else if (variantName == wxT("Croak"))
            variantSwitch = wxT("+croak");

        if (!variantSwitch.IsEmpty())
            cmd << variantSwitch;
    }

    if (GetSpeed() != -1)
        cmd << wxT(" -s ") << GetSpeed();

    if (GetVolume() != -1)
        cmd << wxT(" -a ") << GetVolume();

    if (GetPitch() != -1)
        cmd << wxT(" -p ") << GetPitch();

    if (GetPropertyBool(wxTTS_PROP_ESPEAK_8BIT_ENCODING))
        cmd << wxT(" -b");

    wxString inputFormat =  GetPropertyString(wxTTS_PROP_INPUT_FORMAT);
    if (inputFormat == wxTTS_PROP_INPUT_FORMAT_HTML)
    {
        transformationFlags |= (wxTTS_TRANSFORM_HTML_TO_SSML|wxTTS_TRANSFORM_SPEAK_ORDERED_LIST);
        cmd << wxT(" -m"); // SSML
    }
    else if (inputFormat == wxTTS_PROP_INPUT_FORMAT_SSML || inputFormat == wxTTS_PROP_INPUT_FORMAT_NATIVE)
    {
        transformationFlags |= wxTTS_TRANSFORM_PASS_RAW_TEXT;
        cmd << wxT(" -m"); // SSML
    }
    else
    {
        transformationFlags |= wxTTS_TRANSFORM_TEXT_TO_SSML;
        cmd << wxT(" -m"); // SSML
    }

    if (GetPropertyBool(wxTTS_PROP_ESPEAK_QUIET_OPERATION))
        cmd << wxT(" -q");

    if (GetPropertyBool(wxTTS_PROP_ESPEAK_NO_FINAL_PAUSE))
        cmd << wxT(" -z");

    if (isFilename)
    {
        if (!waveFilename.IsEmpty())
            cmd << wxT(" -w \"") << waveFilename << wxT("\"");

        // First do some substitutions, if we have a transformer.
        wxString filename = DoTransformation(textOrFilename, transformationFlags);

        cmd << wxT(" -f \"") << filename << wxT("\"");
    }
    else
    {
        wxString text(textOrFilename);
        if (GetTransformer())
            GetTransformer()->TransformString(textOrFilename, text, transformationFlags);

        // Create a buffer containing UTF-8 characters.
        // Is this necessary?
        char *buf = new char[text.Length()*2+1];
        strcpy(buf, (const char*) text.mb_str(wxConvUTF8));
        cmd << wxT(" \"") << wxString::FromAscii(buf) << wxT("\"");

        delete[] buf;
    }

    return Launch(cmd, options);
}

// Get a list of the available voices
wxArrayString wxTTSEspeakHandler::GetAvailableVoices() const
{
    wxString dataDir = GetPropertyString(wxTTS_PROP_DATA_LOCATION);
    if (!dataDir.IsEmpty())
        return GetAvailableVoicesForDir(dataDir);
    else
        return wxArrayString();
}

// Get a list of the available voice variants
wxArrayString wxTTSEspeakHandler::GetAvailableVoiceVariants() const
{
    wxArrayString variants;
    variants.Add(wxTRANSLATE("None"));
    variants.Add(wxTRANSLATE("Male 1"));
    variants.Add(wxTRANSLATE("Male 2"));
    variants.Add(wxTRANSLATE("Male 3"));
    variants.Add(wxTRANSLATE("Male 4"));
    variants.Add(wxTRANSLATE("Male 5"));
    variants.Add(wxTRANSLATE("Male 6"));
    variants.Add(wxTRANSLATE("Female 1"));
    variants.Add(wxTRANSLATE("Female 2"));
    variants.Add(wxTRANSLATE("Female 3"));
    variants.Add(wxTRANSLATE("Female 4"));
    variants.Add(wxTRANSLATE("Whisper"));
    variants.Add(wxTRANSLATE("Croak"));

    return variants;
}

// Get a list of the available voices, given the voices directory
wxArrayString wxTTSEspeakHandler::GetAvailableVoicesForDir(const wxString& voicesDir)
{
    wxArrayString voices;
    wxArrayString dirs;

    if (!voicesDir.IsEmpty() && wxDirExists(voicesDir))
    {
        wxDir::GetAllFiles(voicesDir, & voices, wxT("*"), wxDIR_FILES);
        {
            wxDir dir(voicesDir);
            wxArrayString excluding;
            excluding.Add(wxT("!v"));
            excluding.Add(wxT("mb"));
            excluding.Add(wxT("test"));
            
            if (dir.IsOpened())
            {
                wxString filename;
                
                bool cont = dir.GetFirst(&filename, wxT("*"), wxDIR_DIRS);
                while ( cont )
                {
                    if (excluding.Index(filename) == wxNOT_FOUND)
                    {
                        wxString d = wxTextToSpeech::AppendPaths(voicesDir, filename);
                        dirs.Add(d);
                    }
                    
                    cont = dir.GetNext(&filename);
                }
            }
        }

        size_t i;
        for (i = 0; i < dirs.GetCount(); i++)
        {
            wxString folder(dirs[i]);
            wxDir::GetAllFiles(folder, & voices, wxT("*"), wxDIR_FILES);
        }
    }
    size_t i;
    for (i = 0; i < voices.GetCount(); i++)
    {
        voices[i] = wxFileNameFromPath(voices[i]);
    }
    voices.Sort();

    return voices;
}

// Does this handler have the specified capability?
bool wxTTSEspeakHandler::HasCapability(wxTTSCapability capability) const
{
    if (capability == wxTTS_CAP_PROGRAM_LOCATION)
        return true;
    if (capability == wxTTS_CAP_DATA_LOCATION)
        return true;
    if (capability == wxTTS_CAP_WRITE_WAVE_FILE)
        return true;
    if (capability == wxTTS_CAP_SPEAK_HTML)
        return true;
    if (capability == wxTTS_CAP_VOICE_VARIATIONS)
        return true;
    if (capability == wxTTS_CAP_VOICE)
        return true;
    if (capability == wxTTS_CAP_PITCH)
        return true;
    if (capability == wxTTS_CAP_SPEED)
        return true;
    if (capability == wxTTS_CAP_VOLUME)
        return true;
    if (capability == wxTTS_CAP_SSML)
        return false; // ??

    return false;
}

// Get the default voice
wxString wxTTSEspeakHandler::GetDefaultVoice() const
{
    return wxT("en-rp");
}

// Get the default voice variant
wxString wxTTSEspeakHandler::GetDefaultVoiceVariant() const
{
    return wxT("None");
}

/// Set the program location, and on Windows, deduce the data location.
void wxTTSEspeakHandler::SetProgramLocation(const wxString& location)
{
    SetProperty(wxTTS_PROP_PROGRAM_LOCATION, location);

    // Try to guess the data location from the program location
#ifdef __WXMSW__
    wxString oldDataFolder = GetDataLocation();
    if (!location.IsEmpty() && oldDataFolder.IsEmpty())
    {
        wxLogNull logNull;
        wxString p(wxPathOnly(location));
        p += wxT("/../espeak-data/voices");
        wxFileName fname(p);
        fname.Normalize();
        p = fname.GetFullPath();
        
        if (wxDirExists(p))
        {
            SetProperty(wxTTS_PROP_DATA_LOCATION, p);
        }
    }
#endif
}
